package com.cellulam.framework.datasource;

import com.cellulam.framework.core.exceptions.SysException;
import com.cellulam.framework.core.log.LogClient;
import com.cellulam.framework.core.utils.SpringContextUtil;
import com.cellulam.properties.SystemProperties;
import com.google.common.collect.Maps;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Map;

@Configuration
@MapperScan("com.cellulam.dao.daos")
@EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE + 100)
@Order(value = Ordered.HIGHEST_PRECEDENCE + 100)
@Slf4j
@DependsOn("springContextUtil")
public class MybatisConfiguration implements InitializingBean {
    @Autowired
    private SystemProperties systemProperties;

    @Autowired
    private DataSourceConfiguration dataSourceConfiguration;

    @Autowired
    private LogClient logClient;

    private DataSource defaultDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        log.info("--------------------  sqlSessionFactory init ---------------------");
        try {

            SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            //     sessionFactoryBean.setDataSource(roundRobinDataSouce);
            sessionFactoryBean.setDataSource(roundRobinDataSourceProxy());

            // 读取配置
            sessionFactoryBean.setTypeAliasesPackage(systemProperties.getMybatisTypeAliasesPackage());
            sessionFactoryBean.setFailFast(true);

            //设置mapper.xml文件所在位置
            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(systemProperties.getMybatisMapperLocations());
            sessionFactoryBean.setMapperLocations(resources);
            //设置mybatis-config.xml配置文件位置
            sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(systemProperties.getMybatisConfigLocation()));

            DataSourceSqlInterceptor logSqlInterceptor = new DataSourceSqlInterceptor(systemProperties, dataSourceConfiguration);
            logSqlInterceptor.setLogClient(logClient);
            Interceptor[] plugins = new Interceptor[]{logSqlInterceptor};
            sessionFactoryBean.setPlugins(plugins);

            return sessionFactoryBean.getObject();
        } catch (IOException e) {
            log.error("mybatis resolver mapper*xml is error", e);
            return null;
        } catch (Exception e) {
            log.error("mybatis sqlSessionFactoryBean create error", e);
            return null;
        }
    }

    /**
     * 把所有数据库都放在路由中
     *
     * @return
     */
    @Bean(name = "roundRobinDataSourceProxy")
    public AbstractRoutingDataSource roundRobinDataSourceProxy() {

        Map<Object, Object> targetDataSources = buildDataSources();


        //路由类，寻找对应的数据源
        AbstractRoutingDataSource proxy = new AbstractRoutingDataSource() {
            /**
             * 这是AbstractRoutingDataSource类中的一个抽象方法，
             * 而它的返回值是你所要用的数据源dataSource的key值，有了这个key值，
             * targetDataSources就从中取出对应的DataSource，如果找不到，就用配置默认的数据源。
             */
            @Override
            protected Object determineCurrentLookupKey() {
                String typeKey = DataSourceContextHolder.getDataSource();
                log.debug("Switch data source: {}", typeKey);
                return typeKey;
//
//                if (typeKey == null) {
//                    log.debug("Key is null, switch default data source: {}", defaultDataSourceName);
//                    return defaultDataSource;
//                }
//
//                Object dataSource = targetDataSources.get(typeKey);
//                if (dataSource == null) {
//                    throw new SysException("数据源不存在: " + typeKey);
//                }
//                log.debug("Switch data source: {}", typeKey);
//                return dataSource;
            }
        };

        proxy.setDefaultTargetDataSource(defaultDataSource);//默认库
        proxy.setTargetDataSources(targetDataSources);
        return proxy;
    }

    private Map<Object, Object> buildDataSources() {
        String dataSourceNames = systemProperties.getDataSourceNames();
        if (StringUtils.isEmpty(dataSourceNames)) {
            throw new SysException("未配置数据源: spring.datasource.names");
        }

        String[] names = dataSourceNames.split(",");

        Map<Object, Object> dataSources = Maps.newHashMap();
        for (String name : names) {
            name = name.trim();
            HikariConfig hikariConfig = new HikariConfig();

            hikariConfig.setDriverClassName(getDataSourceConfig(name, "driver-class-name"));
            hikariConfig.setJdbcUrl(getDataSourceConfig(name, "url"));
            hikariConfig.setUsername(getDataSourceConfig(name, "username"));
            hikariConfig.setPassword(getDataSourceConfig(name, "password"));
            hikariConfig.setReadOnly(getDataSourceConfig(name, "read-only", Boolean.class));

            hikariConfig.setMinimumIdle(systemProperties.getDbPoolMinimumIdle());
            hikariConfig.setMaximumPoolSize(systemProperties.getDbPoolMaximumPoolSize());
            hikariConfig.setAutoCommit(systemProperties.getDbPoolAutoCommit());
            hikariConfig.setIdleTimeout(systemProperties.getDbPoolIdleTimeout());
            hikariConfig.setMaxLifetime(systemProperties.getDbPoolMaxLifetime());
            hikariConfig.setConnectionTimeout(systemProperties.getDbPoolConnectionTimeout());
            hikariConfig.setConnectionTestQuery(systemProperties.getDbPoolConnectionTestQuery());

            HikariDataSource dataSource = new HikariDataSource(hikariConfig);
            dataSources.put(name, dataSource);
            if (defaultDataSource == null) {
                defaultDataSource = dataSource;
                DataSourceContextHolder.setDefaultDatasource(name, dataSource);
            }
        }
        return dataSources;
    }

    private String getDataSourceConfig(String dataSource, String configName) {
        return systemProperties.getProperty(
                String.format("spring.datasource.hikari.%s.%s",
                        dataSource,
                        configName));
    }


    private <T> T getDataSourceConfig(String dataSource, String configName, Class<T> aClass) {
        return systemProperties.getProperty(
                String.format("spring.datasource.hikari.%s.%s",
                        dataSource,
                        configName), aClass);
    }


    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager((DataSource) SpringContextUtil.getBean("roundRobinDataSourceProxy"));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    }
}

